#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>

#include "zlog.h"
#include "gdsl.h"

#include "ups.h"

static zlog_category_t *zc_ups = NULL;
static char proj_name[32]={0};
static gdsl_list_t ups_list;

static gdsl_element_t ups_alloc_unit(void *unit)
{
	sparam_unit_t *punit = NULL;

	punit = malloc(sizeof(sparam_unit_t));
	if(punit == NULL)
		return NULL;
	memcpy(punit,unit,sizeof(sparam_unit_t));
	if(punit->conf!=NULL)
	{
		void *conf = malloc(punit->size);
		if(conf == NULL)
		{
			free(punit);
			punit = NULL;
		}
		else
		{
			memcpy(conf,punit->conf,punit->size);
			punit->conf = conf;
		}
	}
	return punit;
}
static void ups_free_unit(gdsl_element_t unit)
{
	if(unit != NULL)
	{
		sparam_unit_t *punit = (sparam_unit_t*)unit;
		pthread_rwlock_destroy(&punit->lock);
		if(punit->conf!=NULL)
			free(punit->conf);
		free(punit);
	}
}
static long int ups_compare_unit(gdsl_element_t element, void *data)
{
	sparam_unit_t *punit = (sparam_unit_t*)element;
	int mod_id = (int)data;

	return (punit->desc.module - mod_id);
}

static int ups_compos_fname(const char *mname,int type, char *fname)
{
	char filename[64]={0};
	char prefix[32]={0};
	char suffix[8]={0};

	if(mname == NULL || type < 0 || fname == NULL)
	{
		zlog_error(zc_ups,"invalid params!\n");
		return UPS_EPARA;
	}
	switch(type)
	{
		case EFILE_DEF:
			strcpy(prefix,UPS_DISK_DEF);
			strcpy(suffix,"conf");
			break;
		case EFILE_CUR:
			strcpy(prefix,UPS_DISK_CUR);
			strcpy(suffix,"conf");
			break;
		case EFILE_META:
			strcpy(prefix,UPS_DISK_CUR);
			strcpy(suffix,"meta");
			break;
	}
	snprintf(filename,64,"%s/%s_%s.%s",prefix,proj_name,mname,suffix);
	strcpy(fname,filename);	
	return 0;
}

static int ups_read_disk(const char *file,void *buf, int size)
{
	FILE *fp = NULL;

	if(buf == NULL || size <= 0)
	{
		zlog_error(zc_ups,"invalid params!\n");
		return UPS_EPARA;
	}
	fp = fopen(file,"r");
	if(fp == NULL)
	{
		zlog_error(zc_ups,"fopen %s error:%s\n",file,strerror(errno));
		return UPS_EFAIL;
	}
	if(fread(buf,1,size,fp)!=size)
	{
		zlog_error(zc_ups,"fread %s failed!\n",file);
		fclose(fp);
		return UPS_ERWIO;
	}
	zlog_info(zc_ups,"fread %s success,size = %d\n",file,size);
	fclose(fp);

	return UPS_SUCCESS;
}
static int ups_write_disk(const char *file, void *buf,int size)
{
	FILE *fp = NULL;

	if(buf == NULL || size <= 0)
	{
		zlog_error(zc_ups,"invalid params!\n");
		return UPS_EPARA;
	}
	fp = fopen(file,"w");
	if(fp == NULL)
	{
		zlog_error(zc_ups,"fopen %s error:%s\n",file,strerror(errno));
		return UPS_EFAIL;
	}
	if(fwrite(buf,1,size,fp)!=size)
	{
		zlog_error(zc_ups,"fwrite %s failed!\n",file);
		fclose(fp);
		return UPS_ERWIO;
	}
	zlog_info(zc_ups,"fwrite %s success,size = %d\n",file,size);
	fclose(fp);


	return UPS_SUCCESS;
}

int ups_init_env(const char *project)
{
	zc_ups = zlog_get_category(UPS_LOG_CAT);

	if(access(UPS_DISK_DIR,X_OK))
		mkdir(UPS_DISK_DIR,0777);
	if(!access(UPS_DISK_DIR,F_OK))
	{
		mkdir(UPS_DISK_DEF, 0777);
		mkdir(UPS_DISK_CUR, 0777);
	}
	else
	{
		zlog_error(zc_ups,"ups dir[%s] doesnot exist!\n",UPS_DISK_DIR);
		return UPS_EFAIL;
	}
	if(access(UPS_DISK_DEF,F_OK) || access(UPS_DISK_CUR,F_OK))
	{
		zlog_error(zc_ups,"ups subdir[set0 or set1] doesnot exist!\n");
		return UPS_EFAIL;
	}

	/** user project name as the config file prefix */
	strncpy(proj_name,project,32);

	ups_list = gdsl_list_alloc("ups_list",ups_alloc_unit,ups_free_unit);

	return 0;
}

int ups_init_conf(sparam_desc_t desc, void *deconf, int size)
{
	int ret = 0;

	zlog_info(zc_ups,"init module[%s] parameter set.\n",desc.mname);
	
	if(deconf == NULL || size <= 0)
	{
		zlog_error(zc_ups,"invalid params!\n");
		return UPS_EPARA;
	}

	/** copy default configuration to RAM,build RAM conf object */
	sparam_unit_t ups_unit;
	memset(&ups_unit,0,sizeof(sparam_unit_t));
	memcpy(&ups_unit.desc, &desc, sizeof(desc));
	pthread_rwlock_init(&ups_unit.lock,NULL);
	ups_unit.meta.valid = 1;
	ups_unit.meta.consist = 0;
	ups_unit.conf = deconf;
	ups_unit.size = size;

	/** decide if the module has been inited */
	gdsl_element_t unit = gdsl_list_search(ups_list,ups_compare_unit,(void*)desc.module);
	if(unit != NULL)
	{
		zlog_error(zc_ups,"module [%s:%d] has been inited!\n",desc.mname, desc.module);
		return UPS_EFAIL;
	}

	if(gdsl_list_insert_head(ups_list,&ups_unit)==NULL)
	{
		zlog_error(zc_ups,"insert module set error!\n");
		return UPS_EFAIL;
	}

	/** write default configuration to disk set0 file,only write the file here */
	char filename[64]={0};
	
	ret = ups_compos_fname(desc.mname,EFILE_DEF,filename);
	if(ret == UPS_SUCCESS)
	{
		ret = ups_write_disk(filename,deconf,size);
	}

	return ret;
}

int ups_get_conf(sparam_info_t *info, void *data,int size)
{
	int ret = 0;

	if(info == NULL || info->conf == NULL)
	{
		zlog_error(zc_ups,"invalid param!\n");
		return UPS_EPARA;
	}

	/** get module parameter from RAM list */
	gdsl_element_t unit = gdsl_list_search(ups_list,ups_compare_unit,(void*)info->module);
	if(unit == NULL)
	{
		zlog_error(zc_ups,"get module[%d] doesnot exist!\n",info->module);
		return UPS_EPARA;
	}
	sparam_unit_t *ups_unit = (sparam_unit_t*)unit;

	pthread_rwlock_rdlock(&ups_unit->lock);
	if(ups_unit->size > info->size)
	{
		zlog_error(zc_ups,"need size[%d] > avail size[%d]\n",ups_unit->size,info->size);
		pthread_rwlock_unlock(&ups_unit->lock);
		return UPS_EPARA;
	}
	/** just for backup */
	memcpy(info->conf,ups_unit->conf,ups_unit->size);
	info->size = ups_unit->size;
	memcpy(info->conf,ups_unit->conf,ups_unit->size);
	info->size = ups_unit->size;
	
	if(ups_unit->meta.consist==0)
	{
		zlog_info(zc_ups,"need read from set1 to update ram ups.\n");
		/** check disk module parameter from set1 to use latest params */
		char filename[64]={0};
		ret = ups_compos_fname(ups_unit->desc.mname,EFILE_CUR,filename);
		if(ret == UPS_SUCCESS && !access(filename,R_OK))
		{
			ret = ups_read_disk(filename,ups_unit->conf,ups_unit->size);
			if(ret == UPS_SUCCESS)
			{
				memcpy(info->conf,ups_unit->conf,ups_unit->size);
				info->size = ups_unit->size;
				ups_unit->meta.consist = 1;
			}
			else
			{
				memcpy(ups_unit->conf,info->conf,info->size);
				ups_unit->meta.consist = 1;
			}
		}
	}
	pthread_rwlock_unlock(&ups_unit->lock);

	if(ups_unit->desc.get != NULL)
		return ups_unit->desc.get(info->conf,data,size);
	else
		return 0;
}

int ups_set_conf(sparam_info_t info, void *data,int size)
{
	int ret = 0;

	/** get module parameter from RAM list */
	gdsl_element_t unit = gdsl_list_search(ups_list,ups_compare_unit,(void*)info.module);
	if(unit == NULL)
	{
		zlog_error(zc_ups,"get module[%d] doesnot exist!\n",info.module);
		return UPS_EPARA;
	}
	sparam_unit_t *ups_unit = (sparam_unit_t*)unit;
	if(ups_unit->desc.set != NULL)
		ups_unit->desc.set(info.conf,data,size);

	pthread_rwlock_wrlock(&ups_unit->lock);
	if(ups_unit->size < info.size)
	{
		zlog_error(zc_ups,"avail size[%d] < set size[%d]\n",ups_unit->size,info.size);
		pthread_rwlock_unlock(&ups_unit->lock);
		return UPS_EPARA;
	}
	
	memcpy(ups_unit->conf,info.conf,info.size);
	ups_unit->size = info.size;
	ups_unit->meta.consist = 0;

	if(info.persist==1)
	{
		char filename[64]={0};
		ret = ups_compos_fname(ups_unit->desc.mname,EFILE_CUR,filename);
		if(ret == UPS_SUCCESS)
		{
			ret = ups_write_disk(filename,ups_unit->conf,ups_unit->size);
		}
	}
	pthread_rwlock_unlock(&ups_unit->lock);

	return 0;
}

/** exit to free malloced memory*/
int ups_exit_conf(sparam_desc_t desc)
{
	int ret = 0;

	zlog_info(zc_ups,"exit module %s parameter set.\n",desc.mname);
	if(gdsl_list_delete(ups_list,ups_compare_unit,(void*)desc.module)==NULL)
	{
		zlog_error(zc_ups,"get module[%d] doesnot exist!\n",desc.module);
		return UPS_EPARA;
	}

	return ret;
}

int ups_exit_env(void)
{
	zlog_info(zc_ups,"exit ups.\n");

	gdsl_list_free(ups_list);

	return 0;
}

